package com.mcx.modules.socket;

import com.mcx.modules.common.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.Socket;
import java.util.LinkedList;
import java.util.Queue;

@Slf4j
@Data
public class NetWorkSocket implements Runnable {
    // 单例
    private static NetWorkSocket instance;
    public static NetWorkSocket getInstance() throws IOException {
        if (instance == null)
        {
//            lock(lock_object)
            {
                if (instance == null)
                {
                    instance = new NetWorkSocket();
                }
            }
        }
        return instance;
    }

    //接收消息所需变量
    private byte[] m_ReceiveBuffer = new byte[2048];
    private MMO_DataStream dataStream = new MMO_DataStream();
    // 接收消息的队列
    private Queue<byte[]> m_ReceiveQueue = new LinkedList<byte[]>();
    private int m_ReceiveCount = 0;

    //发送消息所需变量
    //发送消息队列
    private Queue<byte[]> m_SendQueue = new LinkedList<byte[]>();
    //检查队列的委托
//    private Action m_CheckSendQuene;
    //压缩数组的长度界限
    private int m_CompressLen = 200;

    //客户端Socket
    private Socket m_Client;
    //接收数据的线程
    private Thread m_ReceiveThread;

    @Override
    public void run() {
        MMO_DataStream ms = new MMO_DataStream();
        while (true) {
            ReceiveMsg();
            try {
                if (m_ReceiveCount <= 5)
                {
                    m_ReceiveCount++;
                    {
                        if (m_ReceiveQueue.size() > 0)
                        {
                            // 得到队列中的数据包
                            byte[] buffer = m_ReceiveQueue.poll();

                            // 异或之后的数组
                             byte[] bufferNew = new byte[buffer.length - 2];

                             Boolean isCompress = false;
                             short crc = 0;

                            ms.inputStream = new DataInputStream(new ByteArrayInputStream(buffer));
                            //压缩标识
                            isCompress = ms.ReadBool();
                            crc = ms.ReadShort();
                            ms.inputStream.read(bufferNew, 0, bufferNew.length);


                             // 先crc
                             int newCrc = CRC.crc16_modbus(bufferNew, 0, bufferNew.length);
                             if (newCrc==crc)
                            {
                                 // 异或 得到原始数据
                                 bufferNew = SecurityUtil.Xor(bufferNew);

                                 if (isCompress)
                                 {
                                     bufferNew = ZLibUtils.decompress(bufferNew);
                                 }

                                short protoCode = 0;
                                byte[] protoContent = new byte[bufferNew.length-2];


                                // 协议编号
                                ms.inputStream = new DataInputStream(new ByteArrayInputStream(bufferNew));
                                protoCode = ms.ReadShort();
                                ms.inputStream.read(protoContent, 0, protoContent.length);

                                SocketDispatcher.getInstance().Dispatch(protoCode, protoContent);
                                log.info(protoCode+"");

                            }
                             else
                             {
                                 break;
                             }
                        }
                        else
                        {
                            break;
                        }
                    }
                }
                else
                {
                    m_ReceiveCount = 0;
                    break;
                }
            } catch (IOException e) {

            }
        }

    }

    //====================================================

    /// <summary>
    /// 连接到socket服务器
    /// </summary>
    /// <param name="ip"></param>
    /// <param name="port"></param>
    public void Connect(String ip, int port) throws IOException {
        // 如果socket已经存在 并且处于连接中状态 则直接返回
        if (m_Client != null && m_Client.isConnected()) return;

        try
        {
            m_Client = new Socket(ip, port);
//            m_CheckSendQueue = OnCheckSendQueueCallBack;

//            if (OnConnectOK != null)
//            {
//                OnConnectOK();
//            }
            log.info("连接成功");
            dataStream.inputStream = new DataInputStream(m_Client.getInputStream());
            dataStream.outputStream = new DataOutputStream(m_Client.getOutputStream());

            //启动线程 进行接收数据
            m_ReceiveThread = new Thread(this::run);
            m_ReceiveThread.start();
        }
        catch (Exception e)
        {
            log.info("连接失败=" + e.getMessage());
            throw new IOException("连接失败");
        }
    }

    /**
     * 检查队列的委托回调
     */
    private void OnCheckSendQueueCallBack() throws IOException {
//        lock (m_SendQueue)
        {
            //如果队列中有数据包 则发送数据包
            if (m_SendQueue.size() > 0)
            {
                Send(m_SendQueue.poll());
            }
        }
    }

    /**
     * 封装数据包
     * @param data
     * @return
     */
    private byte[] MakeData(byte[] data) {
        Boolean isCompress = data.length > m_CompressLen ? true : false;
        if (isCompress)
        {
            data = ZLibUtils.compress(data);
        }
        data = SecurityUtil.Xor(data);
        int crc = CRC.crc16_modbus(data, 0, data.length);

        MMO_DataStream ms = new MMO_DataStream();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try (DataOutputStream outputStream = new DataOutputStream(out)) {
            ms.outputStream = outputStream;
            ms.WriteShort((short) (data.length+3));
            ms.WriteBool(isCompress);
            ms.WriteShort((short) crc);
            ms.outputStream.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return out.toByteArray();
    }

    /**
     * 发送消息
     * @param buffer
     */
    public void SendMsg(byte[] buffer) throws IOException {
        //得到封装后的数据包
        byte[] sendBuffer = MakeData(buffer);

//        lock (m_SendQueue)
        {
            //把数据包加入队列
            m_SendQueue.offer(sendBuffer);

            //启动委托（执行委托）
            OnCheckSendQueueCallBack();
        }
    }

    /**
     * 真正发送数据包到服务器
     * @param buffer
     */
    private void Send(byte[] buffer) throws IOException {
        dataStream.outputStream.write(buffer, 0, buffer.length);
//        log.info("#========="+buffer.length+"================");
//        for (var i = 0; i < buffer.length; i++)
//        {
//            log.info(buffer[i]+"");
//        }
//        log.info("#================end=======================");
        SendCallBack();
    }

    /**
     * 发送数据包的回调
     */
    private void SendCallBack() throws IOException {
        //继续检查队列
        OnCheckSendQueueCallBack();
    }

    // =======================

    /**
     * 接收数据
     */
    private void ReceiveMsg(){
        try {
            int available = dataStream.inputStream.available();
            if ( available > 0) {
                //已经接收到数据
                //把接收到数据 写入缓冲数据流的尾部
                //把指定长度的字节 写入数据流

                //如果缓存数据流的长度>2 说明至少有个不完整的包过来了
                //为什么这里是2 因为我们客户端封装数据包 用的ushort 长度就是2
                if (available > 2)
                {
                    //进行循环 拆分数据包
                    while (true)
                    {
                        //把数据流指针位置放在0处

                        //currMsgLen = 包体的长度
                        int currMsgLen = dataStream.ReadShort();

                        //currFullMsgLen 总包的长度=包头长度+包体长度
                        int currFullMsgLen = 2 + currMsgLen;

                        //如果数据流的长度>=整包的长度 说明至少收到了一个完整包
                        if (available >= currFullMsgLen)
                        {
                            //至少收到一个完整包

                            //定义包体的byte[]数组
                            byte[] buffer = new byte[currMsgLen];

                            //把数据流指针放到2的位置 也就是包体的位置

                            //把包体读到byte[]数组
                            dataStream.inputStream.read(buffer, 0, currMsgLen);

                            m_ReceiveQueue.offer(buffer);
                            log.info("{}", buffer);

                            //==============处理剩余字节数组===================

                            //剩余字节长度
                            int remainLen = available - currFullMsgLen;
                            if (remainLen > 0)
                            {
                                //把指针放在第一个包的尾部

//                                //定义剩余字节数组
//                                byte[] remainBuffer = new byte[remainLen];
//
//                                //把数据流读到剩余字节数组
//                                inputStream.read(remainBuffer, currFullMsgLen, remainLen);
//
//                                //清空数据流
//                                outputStream.flush();
//
//                                //把剩余字节数组重新写入数据流
//                                outputStream.write(remainBuffer, 0, remainBuffer.length);

//                                remainBuffer = null;
                                break;
                            }
                            else
                            {
                                //没有剩余字节

                                //清空数据流
//                                outputStream.flush();

                                break;
                            }
                        }
                        else
                        {
                            //还没有收到完整包
                            break;
                        }
                    }
                }
                ReceiveMsg();
            }
//            else {
//                //客户端断开连接
//                log.info("客户端{0}断开连接"+  m_Socket.getRemoteSocketAddress().toString());
//                RoleMgr.getInstance().getAllRole().remove(m_Role);
//            }
        } catch (IOException e) {
            e.printStackTrace();
            //客户端断开连接
            log.info("客户端{0}断开连接 原因{1}"+  m_Client.getRemoteSocketAddress().toString()+ e.toString());
        }
    }
}
